package com.xdqzy.baseline.common.aspect;

import com.xdqzy.baseline.common.annotation.CacheDelete;
import com.xdqzy.baseline.common.annotation.CacheParam;
import com.xdqzy.baseline.common.manager.RedisCacheManager;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;

/**
 * @author : zhangdongdong
 * @version V1.0
 * @Title : 缓存操作切面
 * @ClassName: com.xdqzy.baseline.common.aspect.CacheOperateAdvice.java
 * @Description:
 * @date :  2020-10-27 14:49
 */
@Slf4j
public class CacheOperateAdvice extends AbstractPointcutAdvisor {

    @Lazy
    @Resource
    private RedisCacheManager redisCacheManager;

    private final transient Pointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(CacheDelete.class);

    private final transient Advice advice = (MethodInterceptor) invocation -> {
        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        Method method = invocation.getMethod();
        CacheDelete cacheDelete = method.getAnnotation(CacheDelete.class);
        final String module = cacheDelete.value();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] annotatedParameterTypes = method.getParameterAnnotations();
        int paramIndex = 0;
        CacheParam cacheParam = null;
        for (int i = 0; i < parameterTypes.length && cacheParam == null; i++) {
            paramIndex = i;
            for (Annotation type : annotatedParameterTypes[i]) {
                if (type instanceof CacheParam) {
                    cacheParam = (CacheParam) type;
                    break;
                }
            }
        }

        if (cacheParam == null) {
            log.warn("在方法:{}上未找到 @CacheParam 注解的参数", method);
            return invocation.proceed();
        }
        Serializable argument = (Serializable) invocation.getArguments()[paramIndex];
        if (argument == null) {
            log.warn("在方法:{}上注解 @CacheParam 的参数为空", method);
            return invocation.proceed();
        }
        Class<?> parameterType = parameterTypes[paramIndex];
        if (synchronizationActive) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    //有事务就在事务提交后删除
                    removeCache(parameterType, module, argument);
                }
            });
        } else {
            //没有事务就先删除
            removeCache(parameterType, module, argument);
        }
        return invocation.proceed();
    };

    @Override
    public Pointcut getPointcut() {
        return pointcut;
    }


    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }

    private void removeCache(Class<?> parameterType, String module, Serializable argument) {
        if (Collection.class.isAssignableFrom(parameterType)) {
            redisCacheManager.removeCache(module, (Collection<? extends Serializable>) argument);
        } else if (parameterType.isArray()) {
            redisCacheManager.removeCache(module, Arrays.stream((Serializable[]) argument).collect(Collectors.toList()));
        } else {
            redisCacheManager.removeCache(module, Collections.singletonList(argument));
        }
    }
}
